import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:yeild_libs_dal/dal.dart';

class AppScope extends DalScope {
  late UserRepository userRepository;
  late NetRepository netRepository;
  AppScope._() : super();

  factory AppScope.of() {
    return AppScope._()..onCreate();
  }

  @override
  void onCreate() {
    super.onCreate();
    userRepository = UserRepository.of(this, 'app');
    netRepository = NetRepository.of(this);
  }

  void createUserScope() {
    UserScope.of(this)..init();
  }
}

class UserScope extends DalScope {
  late UserRepository userRepository;
  UserScope._(super.scope);

  factory UserScope.of(DalScope scope) {
    return UserScope._(scope)..onCreate();
  }

  @override
  void registerSelf() {
    parent?.registerScope(this);
  }

  @override
  void onCreate() {
    super.onCreate();
    userRepository = UserRepository.of(this, 'user');
  }
}

class NetRepository extends DalRepository {
  NetRepository._(super.scope);

  factory NetRepository.of(DalScope scope) {
    return NetRepository._(scope)..onCreate();
  }

  @override
  void registerSelf() {
    scope.registerRepo(this);
  }
}

class UserRepository extends DalRepository {
  String origin;
  final ValueNotifier<int> count = ValueNotifier(0);

  UserRepository._(super.scope, this.origin);

  factory UserRepository.of(DalScope scope, String origin) {
    return UserRepository._(scope, origin)..onCreate();
  }

  @override
  void registerSelf() {
    scope.registerRepo(this);
  }

  void increment() {
    count.value++;
  }
}

class UserViewModel extends DalViewModel {
  UserRepository? _userRepository;
  final ValueNotifier<int> count = ValueNotifier(-2);

  UserViewModel._(super.scope);

  factory UserViewModel.of(DalScope scope) {
    return UserViewModel._(scope)..onInit();
  }

  void onInit() {
    autoWatchRepo(bindUserRepository);
  }

  void bindUserRepository(UserRepository? userRepository) {
    _userRepository = userRepository;
    autoWatchValue(_userRepository?.count, onCountChanged);
  }

  void onCountChanged(int? value) {
    count.value = value ?? -1;
  }

  void increment() {
    _userRepository?.increment();
  }
}

void main() async {
  var scope = AppScope.of();
  assert(await scope.init());
  var netRepo = scope.repo<NetRepository>();
  test('load net repository', () {
    assert(netRepo is NetRepository);
  });

  var userRepo = scope.repo<UserRepository>();
  test('load user repository within app scope', () {
    assert(userRepo is UserRepository);
    expect(userRepo!.origin, 'app');
  });

  var vmUser = UserViewModel.of(scope);
  test('load user view model within app scope', () {
    expect(userRepo?.count.hasListeners, isTrue);
    expect(vmUser.count.value, 0);
    vmUser.increment();
    expect(userRepo?.count.value, 1);
    expect(vmUser.count.value, 1);
  });

  scope.createUserScope();
  var userScope = scope.scope<UserScope>();
  test('load user scope', () {
    assert(userScope is UserScope);
  });

  var netRepo1 = userScope!.repo<NetRepository>();
  test('load net repository within user scope', () {
    assert(netRepo1 is NetRepository);
    expect(identical(netRepo, netRepo1), isTrue);
  });

  var userRepo1 = userScope.repo<UserRepository>();
  test('load user repository within user scope', () {
    assert(userRepo1 is UserRepository);
    expect(identical(userRepo, userRepo1), isFalse);
    expect(userRepo1!.origin, 'user');
  });

  var vmUser1 = UserViewModel.of(userScope);
  test('load user view model within user scope', () {
    expect(userRepo1?.count.hasListeners, isTrue);
    expect(vmUser1.count.value, 0);
    vmUser1.increment();
    expect(userRepo1?.count.value, 1);
    expect(vmUser1.count.value, 1);
  });

  test('dispose user view model', () {
    vmUser1.dispose();
    expect(userRepo1?.count.hasListeners, isFalse);
  });

  userScope.dispose();
  test('dispose user scope', () {
    expect(scope.scope<UserScope>(), isNull);
  });

  var userRepo3 = scope.repo<UserRepository>();
  test('load user repository after user scope disposed', () {
    assert(userRepo3 is UserRepository);
    expect(identical(userRepo, userRepo3), isTrue);
    expect(userRepo3!.origin, 'app');
  });
}
